<!--
 * @Author: ZJP 2712104231@qq.com
 * @Date: 2024-12-13 10:07:52
 * @LastEditors: ZJP 2712104231@qq.com
 * @LastEditTime: 2025-03-09 13:36:50
 * @FilePath: \zhlx-autoform-ui\src\components\ZHLX-Components\ZHLX-batchImport\index.vue
 * @Description: 介绍文件的作用
 * 
 * Copyright (c) 2024 by zhlx - 2712104231@qq.com, All Rights Reserved. 
-->
<template>
  <el-drawer title="批量导入向导" class="batch-import-dialog" size="100%" v-model="showDialogFlag" append-to-body
    :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
    <template #header>
      <div class="header-box">
        <h3 style="color: #000; margin: 0">批量导入向导</h3>
        <el-steps :active="active" simple>
          <el-step title="选择Excel" :icon="DocumentAdd" />
          <el-step title="数据矫正" :icon="Warning" />
          <el-step title="数据入库" :icon="UploadFilled" />
        </el-steps>
      </div>
    </template>
    <template #default>
      <div class="batch-import-dialog-body">
        <div v-show="active === 0">
          <div class="btns">
            <el-upload 
              ref="upload" 
              class="upload-excel" 
              v-model:file-list="fileList"
              action=""
              :limit="1" 
              accept=".xlsx, .xls"
              :http-request="uploadFile"
            >
              <template #trigger>
                <el-tooltip effect="light" content="删除已选文件可重新选择文件">
                  <el-button v-if="fileList.length" type="primary" disabled>选择文件</el-button>
                </el-tooltip>
                <el-button v-if="!fileList.length" type="primary">选择文件</el-button>
              </template>
              <el-button class="ml-3" @click="onclickCustomExcel">自定义模板下载</el-button>
              <template #tip>
                <div class="el-upload__tip text-red">
                  只支持上传 *.xls, *.xlsx 格式！
                </div>
              </template>
            </el-upload>
          </div>
          <standard-container />
        </div>

        <check-container ref="checkContainerRef" v-show="active === 1" :formId="formId" :bookId="bookId" :recordId="recordId" :subFormId="subFormId" :formTemp="formTemp" :tableObj="tableObj"></check-container>
        <div class="successful" v-show="active === 2">
          <el-icon size="56" color="green"><SuccessFilled /></el-icon>
          <template v-for="(item, index) in successAndErrorTotal" :key="index">
            <p>您成功导入{{ item.success }}条记录到表单【{{item.title}}】中<template v-if="item.error">，导入【{{item.title}}】失败{{ item.error }}条记录</template>！</p>
          </template>
          <el-button v-loading.fullscreen.lock="fullscreenLoading" v-if="showDownloadErrorData" link type="primary" @click="downloadErrorData">导出失败记录</el-button>
        </div>
      </div>
    </template>
    <template #footer>
      <div class="batch-import-dialog-footer">
        <p class="batch-import-dialog-footer-tips">注：整个过程中为避免数据丢失，请勿关闭窗口！</p>
        <div>
          <el-button type="primary" v-show="active > 0 && active < 2" :disabled="btnLoading" @click="prevClick">上一步</el-button>
          <el-tooltip v-if="nextDisabled" effect="light" content="请先选择文件">
            <el-button type="primary" v-show="active < 2" :loading="btnLoading" :disabled="nextDisabled" @click="nextClick">下一步</el-button>
          </el-tooltip>
          <el-button v-else type="primary" v-show="active < 2" :loading="btnLoading" @click="nextClick">下一步</el-button>
          <el-button @click="cancelClick">关闭</el-button>
        </div>
      </div>
    </template>
  </el-drawer>

  <!-- 表头选择 -->
  <select-excel-header v-if="showDialogFlag" ref="SelectExcelHeaderRef" :formId="formId" :bookId="bookId" :recordId="recordId" :subFormId="subFormId" :formTemp="formTemp" :excelFileNameObj="excelFileNameObj"
    @ok="onOkExcelHeader"></select-excel-header>
</template>
<script>
import { ElMessage, ElMessageBox } from 'element-plus'
import { importExcelFile, importSubExcelFile, saveFormListData, errorListExport, saveSubLedgerAfterCheck, subErrorListExport, getFormViewData } from '@/api/formtemplate/index'
import { DocumentAdd, Warning, UploadFilled } from "@element-plus/icons-vue";
import SelectExcelHeader from "./selectExcelHeader.vue";
import StandardContainer from "./standardContainer.vue";
import CheckContainer from "./checkContainer.vue";
import { deepClone } from '@/utils/util';
import { TODAY } from '@/utils/formula-util'
export default {
  name: "ZHLX-batchImport",
  components: {
    SelectExcelHeader,
    StandardContainer,
    CheckContainer,
  },
  props: {
    importSubFormAndUploadDirectly: {
      type: Boolean,
      default: false
    },
    menuName: {
      type: String,
      default: ''
    },
    userInfos: {
      type: Object,
      default: () => ({
        user: {},
      })
    }
  },
  data () {
    return {
      showDialogFlag: false,
      active: 0,
      formTemp: {},
      tableObj: {
        tableColumns: [],
        tableList: [], // 根据表格文件生成的表单数据e
      },
      nextDisabled: true, // 禁用下一步按钮
      btnLoading: false, // 按钮loading状态
      formId: '', // 表单id
      bookId: '', // bookId
      recordId: '', // 表单数据id
      subFormId: '', // 子表单id（唯一标识）
      saveResponseData: {
        successTotal: {}, // 成功导入的数据条数
        errorTotal: {}, // 导入失败的数据条数
        errorList: [], // 导入失败的数据列表
      }, // 保存表单列表数据返回的数据
      fileList: [],
      fullscreenLoading: false,
      DocumentAdd,
      Warning,
      UploadFilled,
    };
  },
  computed: {
    showDownloadErrorData() {
      const errorTotal = this.saveResponseData.errorTotal
      return Object.keys(errorTotal).find(key => errorTotal[key] > 0);
    },
    // 统计错误数据条数
    successAndErrorTotal() {
      const {
        successTotal = {},
        errorTotal = {}
      } = this.saveResponseData
      const allKeys = [...new Set([...Object.keys(successTotal), ...Object.keys(errorTotal)])]
      return allKeys.map(key => ({
        title: key,
        success: successTotal[key] || 0,
        error: errorTotal[key] || 0,
      }))
    },
    // 文件名称
    excelFileNameObj() {
      let obj = {
        menuName: this.menuName,
        today: TODAY()
      }
      if (this.subFormId && this.formTemp?.formDataDomain) {
        obj.subFormName = this.formTemp.formDataDomain.find(field => field.options.name === this.subFormId)?.options?.label
      }
      if(this.userInfos?.user) {
        obj.userName = this.userInfos.user.name || this.userInfos.user.username
      }
      return obj
    }
  },
  watch: {
    fileList: {
      handler(val) {
        if(!val?.length) {
          this.tableList = []
          this.nextDisabled = true
        }
      },
      immediate: true,
      deep: true
    }
  },
  methods: {
    /**
     * 打开批量导入对话框
     * @param {Object} params - 包含表单和记录ID的对象。
     * @param {string} params.formId - 表单ID。
     * @param {string} params.bookId - 书籍ID。
     * @param {string} params.recordId - 记录ID。
     * @param {string} params.subFormId - 子表单ID。
     * @description 此方法用于打开批量导入对话框，并接收表单ID、书籍ID、记录ID和子表单ID作为参数，
     *              将这些ID赋值给组件的数据属性，并显示对话框。
     */
    open ({ formId, bookId, recordId, subFormId }) {
      if (!formId || !bookId) {
        ElMessage.error('缺少表单ID或书籍ID');
        return;
      }
      this.formId = formId;
      this.bookId = bookId;
      this.recordId = recordId;
      this.subFormId = subFormId;
      this.showDialogFlag = true;
      this.getFormView()
    },
    getFormView() {
      getFormViewData({
        formTplId: this.formId,
		    viewId: this.viewId
      }).then(res => {
        this.formTemp = res.data
      })
    },
    /**
     * 处理文件上传操作。
     * @param {Object} data - 包含上传文件信息的对象。
     * 此方法会将文件添加到 FormData 中，并发送上传请求。
     * 上传成功后，更新表格列表数据，启用下一步操作按钮，并停止加载状态。
     */
    async uploadFile (data) {
      let size = data.file.size / 1024 / 1024;
      if (size > 10) {
        this.$message.error('文件大小不能超过10MB');
        this.fileList = []
        return;
      }
      this.btnLoading = true;
      const importApi = this.subFormId ? importSubExcelFile : importExcelFile;
      const formData = new FormData();
      formData.append("file", data.file);
      if (this.recordId) formData.append("recordId", this.recordId);
      importApi({
        formId: this.formId, 
        bookId: this.bookId, 
        version: 1,
        subFormId: this.subFormId
      },formData).then(async (res) => {
        const tableColumns = await this.getColumnsFn(data.file);
        this.tableList = res.data.tableList;
        this.tableObj = {
          tableColumns: tableColumns,
          tableList: res.data.tableList,
        }
        this.nextDisabled = false;
        this.btnLoading = false;
      }).catch(err => {
        // this.$message.error('文件上传失败');
        this.fileList = [];
        this.btnLoading = false;
      })
    },
    async getColumnsFn(file) {
      return new Promise((resolve, reject) => {
        // sheet黑名单
        const blackList = ["人员选择", "组织选择", "组选择", "角色选择", "岗位选择", "注意事项"]
        const reader = new FileReader();
        reader.onload = (e) => {
          const workbook = new ExcelJS.Workbook();
          workbook.xlsx.load(e.target.result).then((workbook) => {
            const tableColumns = {};
            workbook.worksheets.forEach(sheet => {
              if (!blackList.includes(sheet.name)) {
                tableColumns[sheet.name] = []
                const row1 = sheet.getRow(1)._cells
                const row2 = sheet.getRow(2)._cells
                row1.forEach((col, index) => {
                  let column = col.value;
                  if (['checkbox'].includes(row2[index].value)) {
                    const colValueList = col.value.split('-')
                    column = colValueList.length > 1 ? colValueList.slice(0, -1).join('-') : colValueList.join('-')
                  }
                  if (['select'].includes(row2[index].value)) {
                    const colValueList = col.value.split('-')
                    // 如果存在unique表示为主键字段
                    if (colValueList.includes('unique')) {
                      // 长度大于2表示多选类型
                      if (colValueList.length > 2) {
                        column = colValueList.slice(0, -1).join('-')
                      }
                    } else {
                      // 非主键字段若长度大于1表示多选类型
                      if (colValueList.length > 1) {
                        column = colValueList.slice(0, -1).join('-')
                      }
                    }
                  }
                  if(!tableColumns[sheet.name].includes(column)) {
                    tableColumns[sheet.name].push(column)
                  }
                })
              }
            });
            console.log('tableColumns', tableColumns)
            resolve(tableColumns);
          });
        };
        reader.onerror = (error) => {
          reject(error);
        };
        reader.readAsArrayBuffer(file);
      });
    },
    // getColumnsFn(file) {
    //   return new Promise((resolve, reject) => {
    //     // sheet黑名单
    //     const blackList = ["人员选择", "组织选择", "组选择", "角色选择", "岗位选择", "注意事项"]
    //     const reader = new FileReader();
    //     reader.onload = (e) => {
    //       const data = new Uint8Array((e.target).result);
    //       const workbook = XLSX.read(data, { type: 'array' });
    //       // // 获取第一个工作表的名称
    //       const sheetNameList = workbook.SheetNames.filter(item => !blackList.includes(item));
    //       const tableColumns = {};
    //       sheetNameList.forEach(sheetName => {
    //         const worksheet = workbook.Sheets[sheetName];
    //         const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
    //         tableColumns[sheetName] = []
    //         jsonData[0].forEach((item, index) => {
    //           let column = item;
    //           if(['checkbox', 'select'].includes(jsonData[1][index])) {
    //             column = item.split('-')[0]
    //           }
    //           if(!tableColumns[sheetName].includes(column)) {
    //             tableColumns[sheetName].push(column)
    //           }
    //         });
    //       });
    //       resolve(tableColumns);
    //     };

    //     reader.onerror = (error) => {
    //       reject(error);
    //     };

    //     reader.readAsArrayBuffer(file);
    //   });
    // },
    /**
     * 处理下一步点击事件
     * 如果当前激活状态为1，则执行以下操作：
     * 1. 设置按钮加载状态为true
     * 2. 弹出异常提示框，若取消则停止后续操作并重置按钮加载状态
     * 3. 获取表单数据并进行处理
     * 4. 尝试保存表单列表数据，成功后激活状态加1，失败则显示错误消息
     * 若当前激活状态不为1，则直接将激活状态加1
     */
    async nextClick () {
    if (this.active === 1) {
      this.btnLoading = true;
      const formTableList = this.$refs.checkContainerRef.formTableList
      const prompt = await this.exceptionPrompt(formTableList);
      if (!prompt) {
        this.btnLoading = false;
        return;
      }
      await this.saveFormTableList(formTableList)
      this.btnLoading = false;
      } else {
        this.active += 1;
      }
    },
    prevClick () {
      this.active -= 1;
    },
    /**
     * 取消按钮点击事件处理函数
     * @description 当用户点击取消按钮时，此方法会被触发。它会将对话框显示标志位设为false，从而隐藏对话框，并重置活动状态为0。
     */
    cancelClick () {
      if(this.active === 2) {
        this.$emit('success', {
          formId: this.formId,
          bookId: this.bookId,
          recordId: this.recordId,
          subFormId: this.subFormId,
        })
      }
      this.showDialogFlag = false;
      this.active = 0;
      this.tableList = []
      this.nextDisabled = true
      this.btnLoading = false
      this.formId = ''
      this.bookId = ''
      this.recordId = ''
      this.subFormId = ''
      this.fileList = []
      this.saveResponseData = {
        successTotal: {},
        errorTotal: {},
        errorList: [],
      }
    },

    /**
     * 异常提示方法
     * 在执行批量导入操作前，此方法用于检查所有标签页的数据校验情况。
     * 如果存在未通过校验的数据，将弹出确认框询问用户是否继续导入。
     * @function exceptionPrompt
     * @returns {Promise<boolean>} 如果用户选择继续导入或没有未通过校验的数据，则返回true；如果用户取消导入，则返回false。
     */
    async exceptionPrompt (formTableList) {
      return new Promise(async (resolve, reject) => {
        let errNum = 0
        formTableList.forEach(item => {
          errNum += item.errorList.length
        })
        if (errNum > 0) {
          try {
            await this.$confirm("您有" + errNum + "处数据校验不通过，是否继续导入？", "提示", {
              confirmButtonText: "继续导入",
              cancelButtonText: "取消",
              type: "warning",
              customClass: "zhlx-gjz-batch-import-message"
            })
            // 继续导入
            setTimeout(() => {
              resolve(true)
            }, 200)
          } catch (error) {
            // 取消导入
            setTimeout(() => {
              resolve(false)
            }, 200)
          }
        }
        // 没有校验不通过数据
        setTimeout(() => {
          resolve(true)
        }, 200)
      })
    },
    /**
     * 保存表单数据列表
     * 
     * @param {Array} formTableList - 表单数据列表，包含多个表单项及其数据
     * 
     * 该方法会遍历传入的表单数据列表，处理每个表单项的数据，删除不必要的索引字段，并根据表单项的类型构建新的数据结构。
     * 最后，调用相应的 API 方法保存处理后的数据，并处理保存结果。
     * 
     * @throws {Error} 导入失败时抛出错误提示
     */
    async saveFormTableList (formTableList) {
      // 如果是明细表导入并且未开启自动入库
      if (this.subFormId && this.importSubFormAndUploadDirectly === false) {
        this.nonAutomaticUpload(formTableList)
        return
      }
      const tableList = {}
      deepClone(formTableList).forEach(item => {
        if (item.key === 'main') {
          tableList[item.key] = item.data.map(row => {
            delete row.index
            return row
          })
        } else {
          tableList[item.key] = item.data.map(row => {
            delete row.index
            return {
              zhlx_gjz_component_label: item.title,
              zhlx_gjz_component_type: 'sub-form',
              zhlx_gjz_id: '',
              zhlx_gjz_label: '',
              zhlx_gjz_sortOrder: 0,
              zhlx_gjz_subform_value: row
            }
          })
        }
      })
      try {
        const apiMethod = this.subFormId ? saveSubLedgerAfterCheck : saveFormListData
        const saveres = await apiMethod({
          formId: this.formId, 
          bookId: this.bookId, 
          recordId: this.recordId, 
          subFormId: this.subFormId,
          version: 1, 
          tableList
        })
        this.saveResponseData = saveres.data
        this.active += 1;
      } catch (error) {
        // this.$message.error('导入失败');
      }
    },

    nonAutomaticUpload(formTableList) {
      // 是否为空变量
      let isEmpty = true
      const tableList = {}
      const errorList = {}
      deepClone(formTableList).forEach(item => {
        tableList[item.key] = []
        errorList[item.key] = []
        item.data.forEach(row => {
          delete row.index
          const isErrRow = Object.keys(row).some(key => row[key][0].errorMessage)
          const flagList = isErrRow ? errorList[item.key] : tableList[item.key]
          flagList.push({
            zhlx_gjz_component_label: item.title,
            zhlx_gjz_component_type: 'sub-form',
            zhlx_gjz_id: '',
            zhlx_gjz_label: '',
            zhlx_gjz_sortOrder: 0,
            zhlx_gjz_subform_value: row
          })
          if (isEmpty) isEmpty = false
        })
      })
      if (isEmpty) {
        this.$message.error('数据不能为空');
        return
      }
      const successTotal = {}
      const errorTotal = {}
      Object.keys(tableList).forEach(tableId => {
        const total = tableList[tableId]?.length
        if (total) {
          successTotal[tableList[tableId][0].zhlx_gjz_component_label] = total
        }
      })
      Object.keys(errorList).forEach(tableId => {
        const total = errorList[tableId]?.length
        if (total) {
          errorTotal[errorList[tableId][0].zhlx_gjz_component_label] = total
        }
      })
      this.saveResponseData = {
        successTotal,
        errorTotal,
        errorList: errorList
      }
      this.$emit('table-list-change', tableList)
      this.active += 1;
    },
    /**
     * 下载错误数据
     * 
     * 此方法根据当前表单 ID 决定调用不同的 API 方法，获取错误数据并生成 Excel 文件下载。
     * 
     * @returns {void} 无返回值
     */
    downloadErrorData () {
      this.fullscreenLoading = true
      const apiMethod = this.subFormId ? subErrorListExport : errorListExport
      apiMethod({
        formId: this.formId, 
        bookId: this.bookId, 
        recordId: this.recordId, 
        subFormId: this.subFormId,
        version: 1, 
        errorList: this.saveResponseData.errorList
      }).then(res => {
        let blob = new Blob([res], {
          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        });
        let link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = `${this.formTemp?.formName || ''}${this.subFormId ? ('-' + (this.formTemp.formDataDomain.find(field => field.options.name === this.subFormId)?.options?.label || '明细表') + '') : ''}导入失败数据.xlsx`;
        link.click();
        window.URL.revokeObjectURL(link.href);
        this.fullscreenLoading = false;
      }).catch(err => {
        this.fullscreenLoading = false;
      })
    },
    /**
     * 点击自定义 Excel 导入按钮时触发的方法。
     */
    onclickCustomExcel () {
      this.$refs.SelectExcelHeaderRef.open();
    },
    onOkExcelHeader (data) {
      console.log("onOkExcelHeader", data);
    },
  },
};
</script>
<style lang="scss" scoped>
.step-container {
  display: flex;
  justify-content: space-between;
  width: 100%;
}

.header-box {
  display: flex;
  align-items: center;
}

.el-steps {
  width: 50%;
  padding-left: 0;
  padding-right: 0;
  margin: 0 auto;
  user-select: none;
  // 文本不换行
  .el-step__title {
    white-space: nowrap;
  }
  &--simple {
    background: none;
  }
}

.batch-import-dialog-body {
  height: 100%;
}

.batch-import-dialog-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  &-tips {
    color: red;
  }
}

.btns {
  margin-top: 10px;

  .el-button {
    border-radius: 100px;
  }

  .ml-3 {
    margin-left: 6px;
  }
}

.successful {
  text-align: center;
  padding-top: calc(35vh);
  box-sizing: border-box;
}

.text-red {
  color: #777777;
}
</style>